// CompositeView implements a view with three horizontal, equal-sized areas.
// The left-most area is the "source," the middle area is the "destination,"
// and the right-most area is the "result." CompositeView assures that the
// contents of the result area is always generated by compositing the other
// two areas using the compositing mode set in the setOperator: method.
// It is also possible to change the contents, color, and alpha of the
// source and destination areas; see the methods setSourceGray:, 
// setSourceAlpha:, etc.

// CompositeView written by Bruce Blumberg and Ali Ozer, NeXT Developer Support

#import "CompositeView.h"

#import <appkit/Bitmap.h>
#import <appkit/Control.h>
#import <appkit/Matrix.h>
#import <appkit/Window.h>

#import <dpsclient/wraps.h>

@implementation CompositeView

// The possible draw modes for the source.

#define TRIANGLE 0
#define CIRCLE   1
#define DIAMOND  2
#define HEART    3
#define FLOWER   4

// newFrame creates the view, initializes the rectangles that define the
// three areas described above, and creates the bitmaps used for rendering the
// source and destination bitmaps.

+newFrame:(const NXRect *)tF
{
    // Create the view
    self = [super newFrame:tF];

    // Make rectangles for source, destination and result
    sRect = bounds;
    sRect.size.width /= 3.0;
    dRect = sRect;
    dRect.origin.x = sRect.size.width;
    rRect = dRect;
    rRect.origin.x = dRect.origin.x + dRect.size.width;

    // Create bitmap for source image. Bitmaps are flipped by default;
    // make sure we make ours not-flipped.

    source = [Bitmap newSize:sRect.size.width :sRect.size.height
			type:NX_UNIQUEBITMAP];
    [source setFlip:NO];
    
    // Now do the same for the destination...

    destination = [Bitmap newSize:dRect.size.width :dRect.size.height
			type:NX_UNIQUEBITMAP];
    [destination setFlip:NO];

    // Set the default operator and source picture. Also set default 
    // gray and alpha values. You will of course have to make sure
    // the sliders in your Interface Builder window have the same values.
    // (This is a problem; can you see a way to fix it?)

    operator = NX_COPY;
    sourcePicture = TRIANGLE;
    sourceGray = 0.333; // dark gray
    destGray = 0.666;   // light gray
    sourceAlpha = 1.0;  // opaque
    destAlpha = 1.0;    // opaque

    // Create the bitmap images using the initial values set above

    [self drawSource];
    [self drawDestination];

    return self;
}

// drawSource creates the source image in the source bitmap. Note that
// drawSource does not render in the view; it renders in the bitmap only.

-drawSource
{	
    [source lockFocus];
    PScompositerect (0.0, 0.0, sRect.size.width, sRect.size.height, NX_CLEAR);
    PSsetgray(sourceGray);
    PSsetalpha(sourceAlpha);
    PSnewpath();
    switch (sourcePicture) {
	case TRIANGLE: 
 	    PSmoveto (0.0, 0.0);
	    PSlineto (0.0, sRect.size.height);
	    PSlineto (sRect.size.width, sRect.size.height);
	    break;
	case CIRCLE:
	    PSscale (sRect.size.width, sRect.size.height);
	    PSarc (0.5, 0.5, 0.4, 0.0, 360.0);  // diameter is 80% of area
 	    break;
	case DIAMOND:
 	    PSmoveto (0.0, sRect.size.height / 2.0);
	    PSlineto (sRect.size.width / 2.0, 0.0);
	    PSlineto (sRect.size.width, sRect.size.height / 2.0);
	    PSlineto (sRect.size.width / 2.0, sRect.size.height);
	    break;
	case HEART:
	    PSscale (sRect.size.width, sRect.size.height);
	    PSmoveto (0.5, 0.5);
	    PScurveto (0.3, 1.0, 0.0, 0.5, 0.5, 0.1);
	    PSmoveto (0.5, 0.5);			
	    PScurveto (0.7, 1.0, 1.0, 0.5, 0.5, 0.1);  
	    break;
	case FLOWER:
	    PSscale (sRect.size.width, sRect.size.height);
	    PStranslate (0.5, 0.5);
	    PSmoveto (0.0, 0.0); 
            {int cnt;
	     for (cnt = 0; cnt < 6; cnt++) {
		PSrotate (60.0);
		PScurveto (0.4, 0.5, -0.4, 0.5, 0.0, 0.0);
	     }
	    }
	    break;
	default:
	    break;
    }
    PSclosepath();
    PSfill();
    [source unlockFocus];

    return self;
}

// drawDestination creates the destination image in the destination bitmap. 
// Like drawSource, drawDestination only draws in the bitmap, not the view.

-drawDestination
{
    [destination lockFocus];
    PScompositerect (0.0, 0.0, dRect.size.width, dRect.size.height, NX_CLEAR);
    PSsetgray(destGray);
    PSsetalpha(destAlpha);
    PSnewpath();
    PSmoveto(dRect.size.width, 0.0);
    PSlineto(dRect.size.width, dRect.size.height);
    PSlineto(0.0, dRect.size.height);
    PSclosepath();
    PSfill();
    [destination unlockFocus];
    return self;
}

// setSourcePicture allows setting the picture to be drawn in the source
// bitmap. Buttons connected to this method should have tags that are 
// set to the various possible pictures (see the "#define"s, above).
//
// After setting the sourcePicture instance variable, setSourcePicture redraws
// the bitmap and updates the view to reflect the new configuration.

-setSourcePicture:(id)ctl
{
    sourcePicture = [ctl selectedTag];
    [self drawSource];
    [self display];
    return self;
}

// The following four methods set the color or alpha parameters and update
// the source or destination bitmaps and the view to reflect the change.

-setSourceGray:(id)ctl
{
    sourceGray = [ctl floatValue];
    [self drawSource];
    [self display];
    return self;
}

-setDestGray:(id)ctl
{
    destGray = [ctl floatValue];
    [self drawDestination];
    [self display];
    return self;
}

-setSourceAlpha:(id)ctl
{
    sourceAlpha = [ctl floatValue];
    [self drawSource];
    [self display];
    return self;
}

-setDestAlpha:(id)ctl
{
    destAlpha = [ctl floatValue];
    [self drawDestination];
    [self display];
    return self;
}

// The operator method returns the operator currently in use.

-(int)operator {return operator;}


// setOperator sets the operator to be used in the compositing operations
// and updates the view to reflect the change. Note that setOperator needs
// to be connected to a row of buttons.

-setOperator:(id)sender
{	
    char *modeName;
	
    switch ([sender selectedRow]) {
	case 0: operator = NX_COPY;		break;
	case 1: operator = NX_CLEAR; 		break;
	case 2: operator = NX_SOVER; 		break;
	case 3: operator = NX_DOVER;		break;
	case 4: operator = NX_SIN; 		break;
	case 5: operator = NX_DIN; 		break;
	case 6: operator = NX_SOUT;		break;
	case 7: operator = NX_DOUT;		break;
	case 8: operator = NX_SATOP;		break; 
	case 9: operator = NX_DATOP;		break;
	case 10: operator = NX_XOR; 		break;
	case 11: operator = NX_PLUSD;		break;
	case 12: operator = NX_PLUSL;		break;
	default: break;
    }
    [self speedyDraw];

    return self;
}

		
// drawSelf:: simply redisplays the contents of the view. The source and
// destination rectangles are updated from the bitmaps while the result
// rectangle is created by compositing the two bitmaps.

-drawSelf:(NXRect *)r :(int) count
{
    // Erase the whole view
    NXEraseRect(&bounds);

    // Draw the source bitmap and then frame it with black
    [source composite:NX_COPY toPoint:&sRect.origin];
    PSsetgray(NX_BLACK);
    NXFrameRect(&sRect);

    // Draw the destination bitmap and frame it with black 
    [destination composite:NX_COPY toPoint:&dRect.origin];
    PSsetgray(NX_BLACK);
    NXFrameRect(&dRect);

    // And now create the destination image and frame it with black as well
    [destination composite:NX_COPY toPoint:&rRect.origin];
    [source composite:operator toPoint:&rRect.origin];
    PSsetgray(NX_BLACK);
    NXFrameRect(&rRect);

    return self;
}

// speedyDraw provides some efficiency in redisplaying the view by assuming
// that the source and the destination rectangles are already in place.

-speedyDraw
{
    [self lockFocus];
    NXEraseRect(&rRect);
    [destination composite:NX_COPY toPoint:&rRect.origin];
    [source composite:operator toPoint:&rRect.origin];
    NXFrameRect(&rRect);
    [self unlockFocus];
    [[self window] flushWindow];

    return self;
}

@end